UNPKG

@readyplayerme/rpm-react-sdk

Version:

Ready Player Me React SDK

280 lines (273 loc) 12.4 kB
'use strict'; 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;