@readyplayerme/rpm-react-sdk
Version:
Ready Player Me React SDK
280 lines (273 loc) • 12.4 kB
JavaScript
;
var React = require('react');
var visage = require('@readyplayerme/visage');
var EventName;
(function (EventName) {
EventName["FrameReady"] = "v1.frame.ready";
EventName["SubscriptionCreated"] = "v1.subscription.created";
EventName["SubscriptionDeleted"] = "v1.subscription.deleted";
EventName["AvatarExported"] = "v1.avatar.exported";
EventName["UserSet"] = "v1.user.set";
EventName["UserUpdated"] = "v1.user.updated";
EventName["UserLogout"] = "v1.user.logout";
EventName["UserAuthorized"] = "v1.user.authorized";
EventName["AssetUnlock"] = "v1.asset.unlock";
})(EventName || (EventName = {}));
var TextureChannel;
(function (TextureChannel) {
TextureChannel["None"] = "none";
TextureChannel["BaseColor"] = "baseColor";
TextureChannel["Normal"] = "normal";
TextureChannel["MetallicRoughness"] = "metallicRoughness";
TextureChannel["Emissive"] = "emissive";
TextureChannel["Occlusion"] = "occlusion";
})(TextureChannel || (TextureChannel = {}));
var MorphTargets;
(function (MorphTargets) {
MorphTargets["None"] = "none";
MorphTargets["OculusVisemes"] = "Oculus Visemes";
MorphTargets["ARKit"] = "ARKit";
// Visemes
MorphTargets["Viseme_aa"] = "viseme_aa";
MorphTargets["Viseme_E"] = "viseme_E";
MorphTargets["Viseme_I"] = "viseme_I";
MorphTargets["Viseme_O"] = "viseme_O";
MorphTargets["Viseme_U"] = "viseme_U";
MorphTargets["Viseme_CH"] = "viseme_CH";
MorphTargets["Viseme_DD"] = "viseme_DD";
MorphTargets["Viseme_FF"] = "viseme_FF";
MorphTargets["Viseme_kk"] = "viseme_kk";
MorphTargets["Viseme_nn"] = "viseme_nn";
MorphTargets["Viseme_PP"] = "viseme_PP";
MorphTargets["Viseme_RR"] = "viseme_RR";
MorphTargets["Viseme_sil"] = "viseme_sil";
MorphTargets["Viseme_SS"] = "viseme_SS";
MorphTargets["Viseme_TH"] = "viseme_TH";
// ARKit
MorphTargets["BrowDownLeft"] = "browDownLeft";
MorphTargets["BrowDownRight"] = "browDownRight";
MorphTargets["BrowInnerUp"] = "browInnerUp";
MorphTargets["BrowOuterUpLeft"] = "browOuterUpLeft";
MorphTargets["BrowOuterUpRight"] = "browOuterUpRight";
MorphTargets["EyesClosed"] = "eyesClosed";
MorphTargets["EyeBlinkLeft"] = "eyeBlinkLeft";
MorphTargets["EyeBlinkRight"] = "eyeBlinkRight";
MorphTargets["EyeSquintLeft"] = "eyeSquintLeft";
MorphTargets["EyeSquintRight"] = "eyeSquintRight";
MorphTargets["EyeWideLeft"] = "eyeWideLeft";
MorphTargets["EyeWideRight"] = "eyeWideRight";
MorphTargets["EyesLookUp"] = "eyesLookUp";
MorphTargets["EyesLookDown"] = "eyesLookDown";
MorphTargets["EyeLookDownLeft"] = "eyeLookDownLeft";
MorphTargets["EyeLookDownRight"] = "eyeLookDownRight";
MorphTargets["EyeLookUpLeft"] = "eyeLookUpLeft";
MorphTargets["EyeLookUpRight"] = "eyeLookUpRight";
MorphTargets["EyeLookInLeft"] = "eyeLookInLeft";
MorphTargets["EyeLookInRight"] = "eyeLookInRight";
MorphTargets["EyeLookOutLeft"] = "eyeLookOutLeft";
MorphTargets["EyeLookOutRight"] = "eyeLookOutRight";
MorphTargets["JawOpen"] = "jawOpen";
MorphTargets["JawForward"] = "jawForward";
MorphTargets["JawLeft"] = "jawLeft";
MorphTargets["JawRight"] = "jawRight";
MorphTargets["NoseSneerLeft"] = "noseSneerLeft";
MorphTargets["NoseSneerRight"] = "noseSneerRight";
MorphTargets["CheekPuff"] = "cheekPuff";
MorphTargets["CheekSquintLeft"] = "cheekSquintLeft";
MorphTargets["CheekSquintRight"] = "cheekSquintRight";
MorphTargets["MouthSmileLeft"] = "mouthSmileLeft";
MorphTargets["MouthSmileRight"] = "mouthSmileRight";
MorphTargets["MouthOpen"] = "mouthOpen";
MorphTargets["MouthSmile"] = "mouthSmile";
MorphTargets["MouthLeft"] = "mouthLeft";
MorphTargets["MouthRight"] = "mouthRight";
MorphTargets["MouthClose"] = "mouthClose";
MorphTargets["MouthFunnel"] = "mouthFunnel";
MorphTargets["MouthPucker"] = "mouthPucker";
MorphTargets["MouthShrugLower"] = "mouthShrugLower";
MorphTargets["MouthShrugUpper"] = "mouthShrugUpper";
MorphTargets["MouthRollUpper"] = "mouthRollUpper";
MorphTargets["MouthRollLower"] = "mouthRollLower";
MorphTargets["MouthLowerDownLeft"] = "mouthLowerDownLeft";
MorphTargets["MouthLowerDownRight"] = "mouthLowerDownRight";
MorphTargets["MouthUpperUpLeft"] = "mouthUpperUpLeft";
MorphTargets["MouthUpperUpRight"] = "mouthUpperUpRight";
MorphTargets["MouthDimpleLeft"] = "mouthDimpleLeft";
MorphTargets["MouthDimpleRight"] = "mouthDimpleRight";
MorphTargets["MouthStretchLeft"] = "mouthStretchLeft";
MorphTargets["MouthStretchRight"] = "mouthStretchRight";
MorphTargets["MouthPressLeft"] = "mouthPressLeft";
MorphTargets["MouthPressRight"] = "mouthPressRight";
MorphTargets["MouthFrownLeft"] = "mouthFrownLeft";
MorphTargets["MouthFrownRight"] = "mouthFrownRight";
MorphTargets["TongueOut"] = "tongueOut";
})(MorphTargets || (MorphTargets = {}));
/**
* Builds the source URL for Ready Player Me iframe.
* @param subdomain The subdomain to use for the iframe.
* @param editorConfig The editor configuration.
* @returns The source URL for the iframe.
*/
function buildIframeUrl(subdomain, editorConfig) {
let url = `https://${subdomain || `demo`}.readyplayer.me`;
if (editorConfig?.language)
url += `/${editorConfig.language}`;
url += `/avatar?frameApi`;
if (editorConfig?.clearCache)
url += '&clearCache';
if (editorConfig?.quickStart)
url += '&quickStart';
if (editorConfig?.bodyType)
url += `&bodyType=${editorConfig?.bodyType}`;
return url;
}
/**
* Builds avatar URL from the given base URL and avatar configuration. Optimizations are applied to the URL.
* @param base The base URL.
* @param avatarConfig The avatar configuration.
* @returns The avatar URL.
*/
const buildAvatarUrl = (base, avatarConfig) => {
const queryParams = [];
if (avatarConfig?.quality)
queryParams.push(`quality=${avatarConfig.quality}`);
if (avatarConfig?.meshLod)
queryParams.push(`meshLod=${avatarConfig.meshLod}`);
if (avatarConfig?.textureSizeLimit)
queryParams.push(`textureSizeLimit=${avatarConfig.textureSizeLimit}`);
if (avatarConfig?.textureAtlas)
queryParams.push(`textureAtlas=${avatarConfig.textureAtlas}`);
if (avatarConfig?.morphTargets)
queryParams.push(`morphTargets=${avatarConfig.morphTargets.join(',')}`);
if (avatarConfig?.pose)
queryParams.push(`pose=${avatarConfig.pose}`);
if (avatarConfig?.useHands)
queryParams.push(`useHands=${avatarConfig.useHands}`);
if (avatarConfig?.textureChannels)
queryParams.push(`textureChannels=${avatarConfig.textureChannels.join(',')}`);
if (avatarConfig?.useDracoCompression)
queryParams.push(`useDracoCompression=${avatarConfig.useDracoCompression}`);
if (avatarConfig?.useMeshOptCompression)
queryParams.push(`useMeshOptCompression=${avatarConfig.useMeshOptCompression}`);
const query = queryParams.join('&');
return `${base}${query ? `?${query}` : ''}`;
};
/**
* Safely parses the JSON data from the message event.
* @param event The message event.
* @returns The parsed JSON data or null if the data is not valid JSON.
*/
function safeParseJSON(event) {
try {
return JSON.parse(event.data);
}
catch (error) {
return null;
}
}
const style = {
width: '100%',
height: '100%',
border: 'none',
};
const messageEvent = 'message';
const rpmTarget = 'readyplayerme';
/**
* AvatarCreator is a React component that allows you to create an avatar using Ready Player Me and receive avatar URL.
* @param subdomain The subdomain of your Ready Player Me instance.
* @param editorConfig The configuration for the AvatarCreator component.
* @param avatarConfig The configuration for the Avatar GLB file.
* @param onUserSet A callback that is called when a user is set.
* @param onAvatarExported A callback that is called when an avatar is exported.
* @param onUserAuthorized A callback that is called when a user is authorized.
* @param onAssetUnlock A callback that is called when an asset unlock button is pressed in RPM.
* @returns A React component.
*/
const AvatarCreator = ({ subdomain, editorConfig, avatarConfig, onUserSet, onAvatarExported, onUserAuthorized, onAssetUnlock }) => {
const frameRef = React.useRef(null);
const subscribe = (event) => {
const json = safeParseJSON(event);
if (json?.source !== rpmTarget) {
return;
}
switch (json.eventName) {
case EventName.FrameReady:
// Subscribe to all events
frameRef.current?.contentWindow?.postMessage(JSON.stringify({ target: rpmTarget, type: 'subscribe', eventName: 'v1.**' }), '*');
break;
case EventName.UserSet:
onUserSet?.(json.data.id);
break;
case EventName.AvatarExported:
const avatarUrl = buildAvatarUrl(json.data.url, avatarConfig);
onAvatarExported?.(avatarUrl);
break;
case EventName.UserAuthorized:
onUserAuthorized?.(json.data.id);
break;
case EventName.AssetUnlock:
const assetRecord = { userId: json.data.userId, assetId: json.data.assetId };
onAssetUnlock?.(assetRecord);
break;
}
};
const url = buildIframeUrl(subdomain, editorConfig);
React.useEffect(() => {
window.addEventListener(messageEvent, subscribe);
return () => {
window.removeEventListener(messageEvent, subscribe);
};
});
return React.createElement("iframe", { title: "Ready Player Me", ref: frameRef, src: url, style: style, allow: "camera *; clipboard-write" });
};
const containerStyle = {
width: '100%',
height: '100%',
border: 'none',
position: 'relative',
};
const loadingNodeStyle = {
width: '100%',
height: '100%',
display: 'flex',
fontSize: '1.5rem',
alignItems: 'center',
position: 'absolute',
justifyContent: 'center',
fontFamily: 'sans-serif',
};
/**
* AvatarCreatorViewer is a React component that allows you to create an avatar using Ready Player Me and display it in a 3D canvas.
* @param subdomain The subdomain of your Ready Player Me instance.
* @param editorConfig The configuration for the AvatarEditor component.
* @param avatarConfig The configuration for the Avatar GLB file.
* @param viewerConfig The configuration for the AvatarViewer component.
* @param loadingNode A React node that is displayed while the avatar is loading.
* @param onUserSet A callback that is called when a user is set.
* @param onAvatarExported A callback that is called when an avatar is exported.
* @param onAvatarLoaded A callback that is called when an avatar is loaded.
* @param onUserAuthorized A callback that is called when a user is authorized.
* @param onAssetUnlock A callback that is called when an asset unlock button is pressed in RPM.
* @returns A React component.
*/
const AvatarCreatorViewer = ({ subdomain, editorConfig, viewerConfig, avatarConfig, loadingNode, onUserSet, onAvatarExported, onAvatarLoaded, onUserAuthorized, onAssetUnlock }) => {
const [url, setUrl] = React.useState('');
const [loading, setLoading] = React.useState(true);
const handleOnAvatarExported = (url) => {
const avatarUrl = buildAvatarUrl(url, avatarConfig);
setUrl(avatarUrl);
onAvatarExported?.(avatarUrl);
};
const handleOnLoaded = () => {
setLoading(false);
onAvatarLoaded?.();
};
// prettier-ignore
return url === '' ?
React.createElement(AvatarCreator, { subdomain: subdomain, editorConfig: editorConfig, onUserSet: onUserSet, onAvatarExported: handleOnAvatarExported, onAssetUnlock: onAssetUnlock, onUserAuthorized: onUserAuthorized }) :
React.createElement("div", { style: containerStyle },
React.createElement(visage.Avatar, { ...viewerConfig, modelSrc: url, onLoaded: handleOnLoaded, style: { ...viewerConfig?.style, position: 'absolute' } }),
loading && React.createElement("div", { style: loadingNodeStyle }, loadingNode || 'Loading...'));
};
exports.AvatarCreator = AvatarCreator;
exports.AvatarCreatorViewer = AvatarCreatorViewer;