@scalemule/nextjs 0.1.3 → 0.1.5
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 +146 -2
- package/dist/index.d.ts +146 -2
- package/dist/index.js +444 -183
- package/dist/index.mjs +242 -2
- package/package.json +2 -2
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,
|
|
5
|
+
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
5
6
|
|
|
6
7
|
var __defProp = Object.defineProperty;
|
|
7
8
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -2706,6 +2707,31 @@ var StorageService = class extends ServiceModule {
|
|
|
2706
2707
|
async getInfo(fileId, options) {
|
|
2707
2708
|
return this._get(`/files/${fileId}/info`, options);
|
|
2708
2709
|
}
|
|
2710
|
+
/**
|
|
2711
|
+
* Aggregate status for a file — single call returns scan + reserved
|
|
2712
|
+
* optimize/transcode slots + the canonical view URL paths for image
|
|
2713
|
+
* (transform endpoint) and video (HLS playlist) MIME types.
|
|
2714
|
+
*
|
|
2715
|
+
* Foundational primitive for the chat / progressive media read side.
|
|
2716
|
+
* `useFileStatus()` (in `@scalemule/nextjs`) consumes this for both
|
|
2717
|
+
* first-paint and refresh-on-demand scenarios.
|
|
2718
|
+
*
|
|
2719
|
+
* `optimize` and `transcode` are reserved for Phase 3 enrichment. Until
|
|
2720
|
+
* the photo/video services expose internal status endpoints, callers
|
|
2721
|
+
* should attempt the constructed URLs directly to discover readiness
|
|
2722
|
+
* (404 means the pipeline is still running).
|
|
2723
|
+
*
|
|
2724
|
+
* @example
|
|
2725
|
+
* ```ts
|
|
2726
|
+
* const r = await client.storage.getFileStatus(fileId);
|
|
2727
|
+
* if (r.data?.scan.status === 'clean' && r.data.urls.optimized) {
|
|
2728
|
+
* // image: try transform URL for an optimized variant
|
|
2729
|
+
* }
|
|
2730
|
+
* ```
|
|
2731
|
+
*/
|
|
2732
|
+
async getFileStatus(fileId, options) {
|
|
2733
|
+
return this._get(`/files/${fileId}/status`, options);
|
|
2734
|
+
}
|
|
2709
2735
|
/**
|
|
2710
2736
|
* Get a signed view URL for inline display (img src, thumbnails).
|
|
2711
2737
|
* Returns CloudFront signed URL (fast, ~1us) or S3 presigned fallback.
|
|
@@ -8960,6 +8986,220 @@ function useMedia() {
|
|
|
8960
8986
|
);
|
|
8961
8987
|
return { upload, cancelUpload, error, uploading };
|
|
8962
8988
|
}
|
|
8989
|
+
function useFileStatus(options) {
|
|
8990
|
+
const { storage } = useScaleMule();
|
|
8991
|
+
const { fileId, pollIntervalMs = null, disabled = false } = options;
|
|
8992
|
+
const [status, setStatus] = useState(null);
|
|
8993
|
+
const [loading, setLoading] = useState(false);
|
|
8994
|
+
const [error, setError] = useState(null);
|
|
8995
|
+
const requestSeqRef = useRef(0);
|
|
8996
|
+
const fetchStatus = useCallback(async () => {
|
|
8997
|
+
if (!fileId || disabled) return;
|
|
8998
|
+
const seq = ++requestSeqRef.current;
|
|
8999
|
+
setLoading(true);
|
|
9000
|
+
setError(null);
|
|
9001
|
+
try {
|
|
9002
|
+
const r = await storage.getFileStatus(fileId);
|
|
9003
|
+
if (seq !== requestSeqRef.current) return;
|
|
9004
|
+
if (r.error || !r.data) {
|
|
9005
|
+
setError(r.error);
|
|
9006
|
+
return;
|
|
9007
|
+
}
|
|
9008
|
+
setStatus(r.data);
|
|
9009
|
+
} catch (err) {
|
|
9010
|
+
if (seq !== requestSeqRef.current) return;
|
|
9011
|
+
setError(err);
|
|
9012
|
+
} finally {
|
|
9013
|
+
if (seq === requestSeqRef.current) {
|
|
9014
|
+
setLoading(false);
|
|
9015
|
+
}
|
|
9016
|
+
}
|
|
9017
|
+
}, [storage, fileId, disabled]);
|
|
9018
|
+
useEffect(() => {
|
|
9019
|
+
void fetchStatus();
|
|
9020
|
+
}, [fetchStatus]);
|
|
9021
|
+
useEffect(() => {
|
|
9022
|
+
if (!pollIntervalMs || disabled || !fileId) return;
|
|
9023
|
+
const isClean = status?.scan.status === "clean";
|
|
9024
|
+
if (isClean) return;
|
|
9025
|
+
const id = setInterval(() => {
|
|
9026
|
+
void fetchStatus();
|
|
9027
|
+
}, pollIntervalMs);
|
|
9028
|
+
return () => clearInterval(id);
|
|
9029
|
+
}, [pollIntervalMs, disabled, fileId, status?.scan.status, fetchStatus]);
|
|
9030
|
+
const isReady = status?.scan.status === "clean";
|
|
9031
|
+
return { status, loading, error, isReady, refresh: fetchStatus };
|
|
9032
|
+
}
|
|
9033
|
+
function ScaleMuleMedia(props) {
|
|
9034
|
+
const {
|
|
9035
|
+
fileId,
|
|
9036
|
+
mimeType,
|
|
9037
|
+
blobPreview,
|
|
9038
|
+
width,
|
|
9039
|
+
height,
|
|
9040
|
+
className,
|
|
9041
|
+
style,
|
|
9042
|
+
alt,
|
|
9043
|
+
pollIntervalMs = 2e3,
|
|
9044
|
+
renderPlaceholder,
|
|
9045
|
+
renderBlocked,
|
|
9046
|
+
renderOverride
|
|
9047
|
+
} = props;
|
|
9048
|
+
const { status, isReady } = useFileStatus({ fileId, pollIntervalMs });
|
|
9049
|
+
const isImage = mimeType.startsWith("image/");
|
|
9050
|
+
const isVideo = mimeType.startsWith("video/");
|
|
9051
|
+
const isAudio = mimeType.startsWith("audio/");
|
|
9052
|
+
const src = useMemo(() => {
|
|
9053
|
+
const scan = status?.scan.status;
|
|
9054
|
+
if (scan === "threat" || scan === "quarantined") return null;
|
|
9055
|
+
if (status && isReady) {
|
|
9056
|
+
if (isImage && status.urls.optimized) return status.urls.optimized;
|
|
9057
|
+
if (isVideo && status.urls.hls) return status.urls.hls;
|
|
9058
|
+
return status.urls.original ?? null;
|
|
9059
|
+
}
|
|
9060
|
+
if (blobPreview) return blobPreview;
|
|
9061
|
+
return null;
|
|
9062
|
+
}, [status, isReady, isImage, isVideo, blobPreview]);
|
|
9063
|
+
const renderState = useMemo(() => {
|
|
9064
|
+
const scan = status?.scan.status;
|
|
9065
|
+
if (scan === "threat" || scan === "quarantined") return "blocked";
|
|
9066
|
+
if (isReady) return "ready";
|
|
9067
|
+
if (blobPreview) return "preview";
|
|
9068
|
+
return "pending";
|
|
9069
|
+
}, [status?.scan.status, isReady, blobPreview]);
|
|
9070
|
+
const videoRef = React.useRef(null);
|
|
9071
|
+
useEffect(() => {
|
|
9072
|
+
if (!isVideo) return;
|
|
9073
|
+
if (!src) return;
|
|
9074
|
+
if (!src.endsWith(".m3u8")) return;
|
|
9075
|
+
const el = videoRef.current;
|
|
9076
|
+
if (!el) return;
|
|
9077
|
+
if (el.canPlayType("application/vnd.apple.mpegurl")) {
|
|
9078
|
+
el.src = src;
|
|
9079
|
+
return;
|
|
9080
|
+
}
|
|
9081
|
+
let cancelled = false;
|
|
9082
|
+
let hls = null;
|
|
9083
|
+
void (async () => {
|
|
9084
|
+
try {
|
|
9085
|
+
const moduleName = "hls.js";
|
|
9086
|
+
const mod = await import(
|
|
9087
|
+
/* @vite-ignore */
|
|
9088
|
+
/* webpackIgnore: true */
|
|
9089
|
+
moduleName
|
|
9090
|
+
).catch(
|
|
9091
|
+
() => null
|
|
9092
|
+
);
|
|
9093
|
+
if (cancelled || !mod) return;
|
|
9094
|
+
const Hls = mod.default ?? mod;
|
|
9095
|
+
if (Hls.isSupported()) {
|
|
9096
|
+
hls = new Hls();
|
|
9097
|
+
hls.loadSource(src);
|
|
9098
|
+
hls.attachMedia(el);
|
|
9099
|
+
} else {
|
|
9100
|
+
el.src = src;
|
|
9101
|
+
}
|
|
9102
|
+
} catch {
|
|
9103
|
+
}
|
|
9104
|
+
})();
|
|
9105
|
+
return () => {
|
|
9106
|
+
cancelled = true;
|
|
9107
|
+
if (hls) hls.destroy();
|
|
9108
|
+
};
|
|
9109
|
+
}, [isVideo, src]);
|
|
9110
|
+
if (renderOverride) {
|
|
9111
|
+
return /* @__PURE__ */ jsx(Fragment, { children: renderOverride({ src, state: renderState }) });
|
|
9112
|
+
}
|
|
9113
|
+
if (renderState === "blocked") {
|
|
9114
|
+
return /* @__PURE__ */ jsx(Fragment, { children: renderBlocked ? renderBlocked() : /* @__PURE__ */ jsx(
|
|
9115
|
+
"div",
|
|
9116
|
+
{
|
|
9117
|
+
role: "alert",
|
|
9118
|
+
className,
|
|
9119
|
+
style: { padding: 12, color: "#a00", ...style },
|
|
9120
|
+
children: "This file was blocked."
|
|
9121
|
+
}
|
|
9122
|
+
) });
|
|
9123
|
+
}
|
|
9124
|
+
if (renderState === "pending" && !src) {
|
|
9125
|
+
return /* @__PURE__ */ jsx(Fragment, { children: renderPlaceholder ? renderPlaceholder() : /* @__PURE__ */ jsx(
|
|
9126
|
+
"div",
|
|
9127
|
+
{
|
|
9128
|
+
role: "status",
|
|
9129
|
+
"aria-busy": true,
|
|
9130
|
+
className,
|
|
9131
|
+
style: { padding: 12, color: "#666", ...style },
|
|
9132
|
+
children: "Loading\u2026"
|
|
9133
|
+
}
|
|
9134
|
+
) });
|
|
9135
|
+
}
|
|
9136
|
+
if (isImage) {
|
|
9137
|
+
const finalSrc = src && status?.urls.optimized && (width || height) ? appendQuery(src, {
|
|
9138
|
+
width: width ? String(width) : void 0,
|
|
9139
|
+
height: height ? String(height) : void 0,
|
|
9140
|
+
fit: "cover"
|
|
9141
|
+
}) : src;
|
|
9142
|
+
return /* @__PURE__ */ jsx(
|
|
9143
|
+
"img",
|
|
9144
|
+
{
|
|
9145
|
+
src: finalSrc ?? void 0,
|
|
9146
|
+
alt: alt ?? "",
|
|
9147
|
+
width,
|
|
9148
|
+
height,
|
|
9149
|
+
className,
|
|
9150
|
+
style
|
|
9151
|
+
}
|
|
9152
|
+
);
|
|
9153
|
+
}
|
|
9154
|
+
if (isVideo) {
|
|
9155
|
+
return /* @__PURE__ */ jsx(
|
|
9156
|
+
"video",
|
|
9157
|
+
{
|
|
9158
|
+
ref: videoRef,
|
|
9159
|
+
src: src && !src.endsWith(".m3u8") ? src : void 0,
|
|
9160
|
+
controls: true,
|
|
9161
|
+
playsInline: true,
|
|
9162
|
+
preload: "metadata",
|
|
9163
|
+
className,
|
|
9164
|
+
style,
|
|
9165
|
+
"aria-label": alt,
|
|
9166
|
+
width,
|
|
9167
|
+
height
|
|
9168
|
+
}
|
|
9169
|
+
);
|
|
9170
|
+
}
|
|
9171
|
+
if (isAudio) {
|
|
9172
|
+
return /* @__PURE__ */ jsx(
|
|
9173
|
+
"audio",
|
|
9174
|
+
{
|
|
9175
|
+
src: src ?? void 0,
|
|
9176
|
+
controls: true,
|
|
9177
|
+
preload: "metadata",
|
|
9178
|
+
className,
|
|
9179
|
+
style,
|
|
9180
|
+
"aria-label": alt
|
|
9181
|
+
}
|
|
9182
|
+
);
|
|
9183
|
+
}
|
|
9184
|
+
return /* @__PURE__ */ jsx(
|
|
9185
|
+
"a",
|
|
9186
|
+
{
|
|
9187
|
+
href: src ?? "#",
|
|
9188
|
+
target: "_blank",
|
|
9189
|
+
rel: "noopener noreferrer",
|
|
9190
|
+
className,
|
|
9191
|
+
style,
|
|
9192
|
+
children: alt ?? "Download file"
|
|
9193
|
+
}
|
|
9194
|
+
);
|
|
9195
|
+
}
|
|
9196
|
+
function appendQuery(url, params) {
|
|
9197
|
+
const filtered = Object.entries(params).filter(([, v]) => v != null && v !== "");
|
|
9198
|
+
if (filtered.length === 0) return url;
|
|
9199
|
+
const sep = url.includes("?") ? "&" : "?";
|
|
9200
|
+
const qs = new URLSearchParams(filtered).toString();
|
|
9201
|
+
return url + sep + qs;
|
|
9202
|
+
}
|
|
8963
9203
|
|
|
8964
9204
|
// src/hooks/useMoney.ts
|
|
8965
9205
|
var useMoney = useMoneyClient;
|
|
@@ -10746,4 +10986,4 @@ function createSafeLogger(prefix) {
|
|
|
10746
10986
|
};
|
|
10747
10987
|
}
|
|
10748
10988
|
|
|
10749
|
-
export { FeedbackWidget, ScaleMuleApiError, ScaleMuleClient2 as ScaleMuleClient, ScaleMuleProvider, composePhone, createClient, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, useFeedback, useMedia, useMoney, useMoneyClient, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
|
|
10989
|
+
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.
|
|
3
|
+
"version": "0.1.5",
|
|
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",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@scalemule/money": "^0.0.1",
|
|
56
|
-
"@scalemule/sdk": "^0.0.
|
|
56
|
+
"@scalemule/sdk": "^0.0.41"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"next": ">=14.0.0",
|