@scalemule/nextjs 0.1.4 → 0.1.6

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.mjs CHANGED
@@ -1,7 +1,8 @@
1
+ import * as React from 'react';
1
2
  import { createContext, useState, useMemo, useEffect, useCallback, useContext, useRef } from 'react';
2
3
  import { createMoneyClient } from '@scalemule/money';
3
4
  export { MoneyClient, createMoneyClient } from '@scalemule/money';
4
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
6
 
6
7
  var __defProp = Object.defineProperty;
7
8
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -8885,6 +8886,8 @@ function useMedia() {
8885
8886
  setError(null);
8886
8887
  const isPublic = options?.is_public ?? false;
8887
8888
  const mimeType = file.type || "application/octet-stream";
8889
+ const policy = options?.policy ?? "safe_visible";
8890
+ const gateOnPipeline = policy === "safe_public" || policy === "moderated" || policy === "compliance";
8888
8891
  const sharedOpts = {
8889
8892
  filename: options?.filename,
8890
8893
  metadata: options?.metadata,
@@ -8897,6 +8900,9 @@ function useMedia() {
8897
8900
  if (r2.error || !r2.data) {
8898
8901
  throw r2.error ?? { code: "upload_error", message: "Upload failed", status: 0 };
8899
8902
  }
8903
+ if (gateOnPipeline) {
8904
+ await r2.data.optimized_url_promise;
8905
+ }
8900
8906
  return {
8901
8907
  file_id: r2.data.file_id,
8902
8908
  photo_id: r2.data.photo_id,
@@ -8912,6 +8918,9 @@ function useMedia() {
8912
8918
  if (r2.error || !r2.data) {
8913
8919
  throw r2.error ?? { code: "upload_error", message: "Upload failed", status: 0 };
8914
8920
  }
8921
+ if (gateOnPipeline) {
8922
+ await r2.data.hls_url_promise;
8923
+ }
8915
8924
  return {
8916
8925
  file_id: r2.data.file_id,
8917
8926
  photo_id: null,
@@ -9029,6 +9038,176 @@ function useFileStatus(options) {
9029
9038
  const isReady = status?.scan.status === "clean";
9030
9039
  return { status, loading, error, isReady, refresh: fetchStatus };
9031
9040
  }
9041
+ function ScaleMuleMedia(props) {
9042
+ const {
9043
+ fileId,
9044
+ mimeType,
9045
+ blobPreview,
9046
+ width,
9047
+ height,
9048
+ className,
9049
+ style,
9050
+ alt,
9051
+ pollIntervalMs = 2e3,
9052
+ renderPlaceholder,
9053
+ renderBlocked,
9054
+ renderOverride
9055
+ } = props;
9056
+ const { status, isReady } = useFileStatus({ fileId, pollIntervalMs });
9057
+ const isImage = mimeType.startsWith("image/");
9058
+ const isVideo = mimeType.startsWith("video/");
9059
+ const isAudio = mimeType.startsWith("audio/");
9060
+ const src = useMemo(() => {
9061
+ const scan = status?.scan.status;
9062
+ if (scan === "threat" || scan === "quarantined") return null;
9063
+ if (status && isReady) {
9064
+ if (isImage && status.urls.optimized) return status.urls.optimized;
9065
+ if (isVideo && status.urls.hls) return status.urls.hls;
9066
+ return status.urls.original ?? null;
9067
+ }
9068
+ if (blobPreview) return blobPreview;
9069
+ return null;
9070
+ }, [status, isReady, isImage, isVideo, blobPreview]);
9071
+ const renderState = useMemo(() => {
9072
+ const scan = status?.scan.status;
9073
+ if (scan === "threat" || scan === "quarantined") return "blocked";
9074
+ if (isReady) return "ready";
9075
+ if (blobPreview) return "preview";
9076
+ return "pending";
9077
+ }, [status?.scan.status, isReady, blobPreview]);
9078
+ const videoRef = React.useRef(null);
9079
+ useEffect(() => {
9080
+ if (!isVideo) return;
9081
+ if (!src) return;
9082
+ if (!src.endsWith(".m3u8")) return;
9083
+ const el = videoRef.current;
9084
+ if (!el) return;
9085
+ if (el.canPlayType("application/vnd.apple.mpegurl")) {
9086
+ el.src = src;
9087
+ return;
9088
+ }
9089
+ let cancelled = false;
9090
+ let hls = null;
9091
+ void (async () => {
9092
+ try {
9093
+ const moduleName = "hls.js";
9094
+ const mod = await import(
9095
+ /* @vite-ignore */
9096
+ /* webpackIgnore: true */
9097
+ moduleName
9098
+ ).catch(
9099
+ () => null
9100
+ );
9101
+ if (cancelled || !mod) return;
9102
+ const Hls = mod.default ?? mod;
9103
+ if (Hls.isSupported()) {
9104
+ hls = new Hls();
9105
+ hls.loadSource(src);
9106
+ hls.attachMedia(el);
9107
+ } else {
9108
+ el.src = src;
9109
+ }
9110
+ } catch {
9111
+ }
9112
+ })();
9113
+ return () => {
9114
+ cancelled = true;
9115
+ if (hls) hls.destroy();
9116
+ };
9117
+ }, [isVideo, src]);
9118
+ if (renderOverride) {
9119
+ return /* @__PURE__ */ jsx(Fragment, { children: renderOverride({ src, state: renderState }) });
9120
+ }
9121
+ if (renderState === "blocked") {
9122
+ return /* @__PURE__ */ jsx(Fragment, { children: renderBlocked ? renderBlocked() : /* @__PURE__ */ jsx(
9123
+ "div",
9124
+ {
9125
+ role: "alert",
9126
+ className,
9127
+ style: { padding: 12, color: "#a00", ...style },
9128
+ children: "This file was blocked."
9129
+ }
9130
+ ) });
9131
+ }
9132
+ if (renderState === "pending" && !src) {
9133
+ return /* @__PURE__ */ jsx(Fragment, { children: renderPlaceholder ? renderPlaceholder() : /* @__PURE__ */ jsx(
9134
+ "div",
9135
+ {
9136
+ role: "status",
9137
+ "aria-busy": true,
9138
+ className,
9139
+ style: { padding: 12, color: "#666", ...style },
9140
+ children: "Loading\u2026"
9141
+ }
9142
+ ) });
9143
+ }
9144
+ if (isImage) {
9145
+ const finalSrc = src && status?.urls.optimized && (width || height) ? appendQuery(src, {
9146
+ width: width ? String(width) : void 0,
9147
+ height: height ? String(height) : void 0,
9148
+ fit: "cover"
9149
+ }) : src;
9150
+ return /* @__PURE__ */ jsx(
9151
+ "img",
9152
+ {
9153
+ src: finalSrc ?? void 0,
9154
+ alt: alt ?? "",
9155
+ width,
9156
+ height,
9157
+ className,
9158
+ style
9159
+ }
9160
+ );
9161
+ }
9162
+ if (isVideo) {
9163
+ return /* @__PURE__ */ jsx(
9164
+ "video",
9165
+ {
9166
+ ref: videoRef,
9167
+ src: src && !src.endsWith(".m3u8") ? src : void 0,
9168
+ controls: true,
9169
+ playsInline: true,
9170
+ preload: "metadata",
9171
+ className,
9172
+ style,
9173
+ "aria-label": alt,
9174
+ width,
9175
+ height
9176
+ }
9177
+ );
9178
+ }
9179
+ if (isAudio) {
9180
+ return /* @__PURE__ */ jsx(
9181
+ "audio",
9182
+ {
9183
+ src: src ?? void 0,
9184
+ controls: true,
9185
+ preload: "metadata",
9186
+ className,
9187
+ style,
9188
+ "aria-label": alt
9189
+ }
9190
+ );
9191
+ }
9192
+ return /* @__PURE__ */ jsx(
9193
+ "a",
9194
+ {
9195
+ href: src ?? "#",
9196
+ target: "_blank",
9197
+ rel: "noopener noreferrer",
9198
+ className,
9199
+ style,
9200
+ children: alt ?? "Download file"
9201
+ }
9202
+ );
9203
+ }
9204
+ function appendQuery(url, params) {
9205
+ const filtered = Object.entries(params).filter(([, v]) => v != null && v !== "");
9206
+ if (filtered.length === 0) return url;
9207
+ const sep = url.includes("?") ? "&" : "?";
9208
+ const qs = new URLSearchParams(filtered).toString();
9209
+ return url + sep + qs;
9210
+ }
9032
9211
 
9033
9212
  // src/hooks/useMoney.ts
9034
9213
  var useMoney = useMoneyClient;
@@ -10815,4 +10994,4 @@ function createSafeLogger(prefix) {
10815
10994
  };
10816
10995
  }
10817
10996
 
10818
- export { FeedbackWidget, ScaleMuleApiError, ScaleMuleClient2 as ScaleMuleClient, 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 };
10997
+ 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/nextjs",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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",