@funkit/connect
Version:
Funkit Connect SDK elevates DeFi apps via web2 sign-ins and one-click checkouts.
231 lines (225 loc) • 6.21 kB
JavaScript
"use client";
import {
Box
} from "./chunk-LUHEVXPD.js";
import {
logger
} from "./chunk-AHYOV7AV.js";
// src/components/AsyncImage/useAsyncImage.ts
import { useEffect, useReducer } from "react";
var cachedUrls = /* @__PURE__ */ new Map();
var cachedRequestPromises = /* @__PURE__ */ new Map();
async function loadAsyncImage(asyncImage) {
const cachedRequestPromise = cachedRequestPromises.get(asyncImage);
if (cachedRequestPromise) {
return cachedRequestPromise;
}
const load = async () => asyncImage().then(async (url) => {
cachedUrls.set(asyncImage, url);
return url;
});
const requestPromise = load().catch(async () => {
return load().catch(() => {
cachedRequestPromises.delete(asyncImage);
return void 0;
});
});
cachedRequestPromises.set(asyncImage, requestPromise);
return requestPromise;
}
function useForceUpdate() {
const [, forceUpdate] = useReducer((x) => x + 1, 0);
return forceUpdate;
}
function useAsyncImage(url) {
const cachedUrl = typeof url === "function" ? cachedUrls.get(url) : void 0;
const forceUpdate = useForceUpdate();
useEffect(() => {
if (typeof url === "function" && !cachedUrl) {
loadAsyncImage(url).then(forceUpdate);
}
}, [url, cachedUrl, forceUpdate]);
return typeof url === "function" ? cachedUrl : url;
}
var REMOTE_IMG_CACHE = /* @__PURE__ */ new Set();
function preloadRemoteImages(...imageUrls) {
for (const imageUrl of imageUrls) {
try {
const { protocol } = new URL(imageUrl);
if (protocol !== "https:" && protocol !== "http:") {
logger.warn("preloadRemoteImages:invalidUrl", {
message: "Invalid protocol",
imageUrl
});
continue;
}
if (!REMOTE_IMG_CACHE.has(imageUrl)) {
REMOTE_IMG_CACHE.add(imageUrl);
const preloadImage = new Image();
preloadImage.src = imageUrl;
}
} catch (error) {
logger.warn("preloadRemoteImages:error", {
message: "Unable to preload image",
imageUrl,
error
});
}
}
}
// src/components/AsyncImage/AsyncImage.tsx
import React, { useMemo, useReducer as useReducer2, useState } from "react";
// src/utils/sanitizeUrl.ts
var SAFE_URL_PROTOCOLS = /* @__PURE__ */ new Set(["https:", "http:"]);
function sanitizeUrl(url) {
if (!url) {
return void 0;
}
try {
const parsed = new URL(url);
if (SAFE_URL_PROTOCOLS.has(parsed.protocol)) {
return parsed.href;
}
return void 0;
} catch {
return void 0;
}
}
function sanitizeCssUrl(url) {
const safeUrl = sanitizeUrl(url);
if (!safeUrl) {
return void 0;
}
return safeUrl.replace(/['()\\]/g, (char) => {
return `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`;
});
}
// src/components/AsyncImage/AsyncImage.tsx
function useTriggeredOnce() {
return useReducer2(() => true, false);
}
function AsyncImage({
alt,
background,
borderColor,
borderRadius,
boxShadow,
height,
useAsImage,
src: srcProp,
fallbackSrc,
fallbackElement,
width,
testId
}) {
const src = useAsyncImage(srcProp || fallbackSrc);
const isRemoteImage = src !== void 0 && /^(https?|data):/.test(src);
const [isRemoteImageLoaded, setRemoteImageLoaded] = useTriggeredOnce();
const [hasError, setHasError] = useState(
srcProp === void 0 && fallbackSrc === void 0
);
const { asyncStyle, imgProps } = useMemo(() => {
const asyncStyle2 = !useAsImage ? getRemoteImageStyles(isRemoteImage, isRemoteImageLoaded, src) : {};
if (useAsImage || isRemoteImage) {
const imgProps2 = {
as: "img",
src,
"aria-hidden": true,
onError: ({
currentTarget
}) => {
currentTarget.onerror = null;
if (fallbackSrc !== void 0) {
currentTarget.src = fallbackSrc;
} else {
setHasError(true);
}
},
onLoad: isRemoteImage ? setRemoteImageLoaded : void 0
};
return { imgProps: imgProps2, asyncStyle: asyncStyle2 };
}
return {
imgProps: {
backgroundSize: "cover"
},
asyncStyle: {
...asyncStyle2,
// has to be added as a inline style because vanilla-extract doesn't recognize this as a valid style
backgroundPosition: "center"
}
};
}, [
fallbackSrc,
isRemoteImage,
isRemoteImageLoaded,
src,
useAsImage,
setRemoteImageLoaded
]);
const mainElement = hasError && fallbackElement || /* @__PURE__ */ React.createElement(
Box,
{
...imgProps,
height: "full",
position: "absolute",
style: {
WebkitTouchCallout: "none",
transition: "opacity .15s linear",
userSelect: "none",
...asyncStyle
},
width: "full"
}
);
return /* @__PURE__ */ React.createElement(
Box,
{
"aria-label": alt,
borderRadius,
boxShadow,
height: typeof height === "string" ? height : void 0,
overflow: "hidden",
position: "relative",
role: "img",
style: {
background,
height: typeof height === "number" ? height : void 0,
width: typeof width === "number" ? width : void 0
},
width: typeof width === "string" ? width : void 0,
testId
},
mainElement,
borderColor ? /* @__PURE__ */ React.createElement(
Box,
{
...typeof borderColor === "object" && "custom" in borderColor ? { style: { borderColor: borderColor.custom } } : { borderColor },
borderRadius,
borderStyle: "solid",
borderWidth: "1",
height: "full",
position: "relative",
width: "full"
}
) : null
);
}
var getRemoteImageStyles = (isRemoteImage, isRemoteImageLoaded, src) => {
if (isRemoteImage) {
return {
opacity: isRemoteImageLoaded ? 1 : 0
};
}
const safeSrc = sanitizeCssUrl(src);
return {
// note the url must have quotation, see https://github.com/evanw/esbuild/issues/1843#issuecomment-1370108070
backgroundImage: safeSrc ? `url('${safeSrc}')` : void 0,
backgroundRepeat: "no-repeat",
opacity: src ? 1 : 0
};
};
export {
preloadRemoteImages,
AsyncImage
};